// Copyright (C) Jari Hautio <jari.hautio@iki.fi> 2008. Licensed under GPLv2. See LICENSE.txt in solution folder.
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.IO;
using System.Drawing.Imaging;
using ColorLib;

namespace RyijyApp
{

    /// <summary>
    /// Represents custom color palette that is used for color matching.
    /// </summary>
    public class CustomPalette
    {
        /// <summary>
        /// Groups information about single custom color such as name and color value.
        /// </summary>
        public class ColorItem
        {
            public ColorItem() { }
            public ColorItem(ColorItem ci) { name = ci.Name; text = ci.Text; color = ci.Color; }
            public ColorItem(string n, string t, Color c)
            {
                name = n;
                text = t;
                color = c;
            }
            string name = "";
            string text = "";
            Color color = Color.FromArgb(0,0,0);
            int index=0;
            public string Name { get { return name; } set { name = value;}}
            public string Text { get { return text; } set { text = value;}}
            public Color Color { get { return color; } set { color = value;}}
            public int Index { get { return index;} set { index = value;}}
            public override string ToString()
            {
                return Text;
            }
        }

        /// <summary>
        /// List of all custom colors
        /// </summary>
        List<ColorItem> colors = new List<ColorItem>();
        FastColorTable colorCache = null;
        /// <summary>
        /// Friendly name for palette
        /// </summary>
        string paletteName = "";
        public string PaletteName { get { return paletteName; } set { paletteName = value; } }

        /// <summary>
        /// Filename for the palette with full path
        /// </summary>
        string fileName = "";
        public string FileName { get { return fileName; } set { fileName = value; } }

        string comments = "#Name\tDescription\tRed\tGreen\tBlue\n";
        public string Comments { get { return comments; } }

        public List<ColorItem> Colors { get { return colors; } set { colors = value; colorCache = null; colorSearchList = null; } }
        public event EventHandler ColorsChanged;
        protected void OnColorsChanged()
        {
            colorCache = null;
            if (ColorsChanged != null)
                ColorsChanged(this, EventArgs.Empty);
        }

        private List<int> colorSearchList = null;
        public CustomPalette()
        {
        }

        public CustomPalette(CustomPalette cp)
        {
            foreach (ColorItem ci in cp.colors)
            {
                colors.Add(new ColorItem(ci));
            }
            paletteName = cp.paletteName;
            fileName = cp.fileName;
        }


        public override string ToString()
        {
            return PaletteName;
        }

        private bool userPalette = false;

        public bool UserPalette
        {
            get { return userPalette; }
            set { userPalette = value; }
        }
	

        /// <summary>
        /// Loads custom colors from tab separated text file.
        /// First line is considered header line and is not processed.
        /// Following lines must have format:
        /// colorname\tlongname\tRed\tGreen\tBlue
        /// where Red Green and Blue values are in range 0 - 255.
        /// Throws file exception if loading the file fails.
        /// </summary>
        /// <param name="file">Custom colors file name</param>
        /// <returns>True if palette was loaded succesfully.</returns>
        public bool Load(string file)
        {
            int n = file.LastIndexOf('\\');
            fileName = file;
            paletteName = (n<0) ? file : file.Substring(n + 1);
            if (paletteName.EndsWith("_palette.txt"))
                paletteName = paletteName.Substring(0, paletteName.Length - 12);
            using (TextReader tr = new System.IO.StreamReader(file))
            {
                return Load(tr);
            }
        }

        /// <summary>
        /// Loads custom colors from tab separated text reader.
        /// Lines starting with # are comments and are not processed.
        /// Color lines must have format:
        /// colorname\tlongname\tRed\tGreen\tBlue
        /// where Red Green and Blue values are in range 0 - 255.
        /// </summary>
        /// <param name="file">Custom colors file name</param>
        /// <returns>True if palette was loaded succesfully.</returns>
        public bool Load(TextReader tr)
        {
            colorSearchList = null;
            char[] separator = new char[] { '\t' };
            string line;
            comments = "";
            for (line = tr.ReadLine(); line != null;line = tr.ReadLine() )
            {
                if (line.StartsWith("#"))
                {
                    if (colors.Count == 0)
                        comments = comments + line + "\n";
                    continue;
                }
                try
                {
                    string[] ar = line.Split(separator);
                    if (ar.Length == 5)
                    {
                        ColorItem item = new ColorItem(ar[0], ar[1],
                            Color.FromArgb(
                                Convert.ToByte(ar[2]),
                                Convert.ToByte(ar[3]),
                                Convert.ToByte(ar[4])));
                        item.Index = colors.Count;
                        colors.Add(item);
                    }
                }
                catch (Exception)
                {
                    return false;
                }
            }
            OnColorsChanged();
            return true;
        }
        public void Save()
        {
            Save(FileName);
        }
        public void Save(string newFileName)
        {
            fileName = newFileName;
            using (TextWriter tw = new System.IO.StreamWriter(newFileName))
            {
                Save(tw);
            }
        }
        public void Save(TextWriter tw)
        {
            tw.WriteLine(comments);
            foreach (ColorItem c in colors)
            {
                tw.WriteLine("{0}\t{1}\t{2}\t{3}\t{4}",
                    c.Name, c.Text,
                    c.Color.R.ToString(), 
                    c.Color.G.ToString(), 
                    c.Color.B.ToString());
            }
        }
        /// <summary>
        /// Returns short name for given color or empty string if not found.
        /// </summary>
        /// <param name="c">Custom color</param>
        /// <returns>Color coded name</returns>
        public string GetName(Color c)
        {
            foreach (ColorItem ci in colors)
            {
                if (ci.Color == c)
                    return ci.Name;
            }
            return "";
        }
        /// <summary>
        /// Returns long name for given color or empty string if not found.
        /// </summary>
        /// <param name="c">Custom color</param>
        /// <returns>Color long name</returns>
        public string GetText(Color c)
        {
            foreach (ColorItem ci in colors)
            {
                if (ci.Color == c)
                    return ci.Text;
            }
            return "";
        }
        public string GetTextAndName(Color c)
        {
            foreach (ColorItem ci in colors)
            {
                if (ci.Color == c)
                    return ci.Name +" " + ci.Text;
            }
            return "";
        }
        /// <summary>
        /// Returns all color values as list.
        /// </summary>
        /// <returns>List of cusrom colors</returns>
        public List<Color> GetColors()
        {
            List<Color> l = new List<Color>();
            foreach (ColorItem ci in colors)
            {
                l.Add(ci.Color);
            }
            return l;
        }
        /// <summary>
        /// Calculates distance betweemn two colors.
        /// </summary>
        /// <param name="c1"></param>
        /// <param name="c2"></param>
        /// <returns></returns>
        double Distance(Color c1, Color c2)
        {
            return ColorMath.DistanceLab(c1, c2);
        }
        /// <summary>
        /// Finds best matching color from the palette.
        /// </summary>
        /// <param name="c">Color to be matched</param>
        /// <returns>Best match color</returns>
        public Color FindMatch(Color c)
        {
            Color best = c; 
            double dist = 256 * 256 * 4 + 1000;
            foreach (ColorItem ci in colors)
            {
                double d = Distance(c, ci.Color);
                if (d < dist)
                {
                    dist = d;
                    best = ci.Color;
                    if (dist == 0) break;
                }
            }
            return best;
        }
        /// <summary>
        /// Finds best matching color from the palette.
        /// </summary>
        /// <param name="c">Color to be matched</param>
        /// <returns>Best match color</returns>
        public Color FindMatch(Color c, List<Color> colorList)
        {
            Color best = c;
            double dist = 256 * 256 * 4 + 1000;
            foreach (Color c2 in colorList)
            {
                double d = Distance(c, c2);
                if (d < dist)
                {
                    dist = d;
                    best = c2;
                    if (dist == 0) break;
                }
            }
            return best;
        }
        public ColorItem AddColor(Color c, string name, string text)
        {
            ColorItem ci = new ColorItem(name, text, c);
            ci.Index = colors.Count;
            colors.Add(ci);
            if (colorSearchList == null)
                colorSearchList = new List<int>();
            colorSearchList.Add(c.ToArgb());
            colorSearchList.Sort();
            OnColorsChanged();
            return ci;
        }
        public class ItemComparer : IComparer<ColorItem>
        {
            public enum Mode{ Color, Name, Text, Index };
            Mode mode = Mode.Color;
            bool backwards = false;
            public ItemComparer()
            {
            }
            public ItemComparer(Mode m, bool back)
            {
                mode = m;
                backwards = back;
            }
            #region IComparer<ColorItem> Members

            public int Compare(ColorItem x, ColorItem y)
            {
                if (backwards)
                {
                    ColorItem t = x;
                    x = y;
                    y = t;
                }
                switch (mode)
                {
                    case Mode.Name:
                        return Comparer<string>.Default.Compare(x.Name, y.Name);
                    case Mode.Text:
                        return Comparer<string>.Default.Compare(x.Text, y.Text);
                    case Mode.Index:
                        return Comparer<int>.Default.Compare(x.Index, y.Index);
                }
                int a = x.Color.ToArgb();
                int b = y.Color.ToArgb();
                return (a == b) ? 0 : (a < b ? -1 : 1);
            }

            #endregion
        }
        public bool Contains(Color c)
        {
            if (colorSearchList == null ||
                colorSearchList.Count != colors.Count)
            {
                colorSearchList = new List<int>();
                foreach (ColorItem ci in colors)
                    colorSearchList.Add(ci.Color.ToArgb());
                colorSearchList.Sort();
            }
            return colorSearchList.BinarySearch(c.ToArgb()) >= 0;
        }
        /// <summary>
        /// Get color conversion map
        /// </summary>
        /// <param name="histogram">input colors</param>
        /// <returns></returns>
        public System.Drawing.Imaging.ColorMap[] GetColorMap(Dictionary<Color, int> histogram)
        {
            System.Drawing.Imaging.ColorMap[] map = new System.Drawing.Imaging.ColorMap[histogram.Keys.Count];
            int i=0;
            ColorCube cube = new ColorCube();
            foreach (ColorItem c in colors)
                cube.Add(c.Color);
            foreach (Color c in histogram.Keys)
            {
                map[i] = new System.Drawing.Imaging.ColorMap();
                map[i].OldColor = c;
                map[i].NewColor = FindMatch(c, cube.Find(c));

                i++;
            }
            return map;
        }
        /// <summary>
        /// Converts image to palette colors.
        /// </summary>
        /// <param name="numColors">Color histogram after replacing the colors.</param>
        public Dictionary<Color, int> Process(Bitmap bm)
        {
            return ProcessFast(bm);
        }
        /// <summary>
        /// Converts image to palette colors.
        /// </summary>
        /// <param name="numColors">Color histogram after replacing the colors.</param>
        public Dictionary<Color, int> ProcessSlow(Bitmap bm)
        {
            // update histogram
//            DateTime t0 = DateTime.Now;
            Dictionary<Color, int> dc = new Dictionary<Color, int>();
            if (bm == null) return dc;
            // collect histogram first
            ColorCube cube = new ColorCube();
            foreach (ColorItem c in colors)
                cube.Add(c.Color);
            int width = bm.Width;
            int height = bm.Height;
            for (int x = 0; x < width; x++)
                for (int y = 0; y < height; y++)
                {
                    Color c = bm.GetPixel(x, y);
                    c = FindMatch(c, cube.Find(c));
                    bm.SetPixel(x, y, c);
                    if (dc.ContainsKey(c)) dc[c]++;
                    else dc.Add(c, 1);
                }
//            DateTime t1 = DateTime.Now;
//            System.Windows.Forms.MessageBox.Show(
//                String.Format("{0}", t1 - t0));
            return dc;
        }
        /// <summary>
        /// Converts image to palette colors.
        /// </summary>
        /// <param name="numColors">Color histogram after replacing the colors.</param>
        public Dictionary<Color, int> ProcessFast(Bitmap bm)
        {
            if (colorCache == null)
            {
                colorCache = new FastColorTable(6);
            }
            // update histogram
            //            DateTime t0 = DateTime.Now;
            Dictionary<Color, int> dc = new Dictionary<Color, int>();
            if (bm == null) return dc;
            // collect histogram first
            CubePicker cube = new CubePicker(GetColors(), CubePicker.CubeMode.Search, CubePicker.ColorMode.Lab);
            
            int width = bm.Width;
            int height = bm.Height;
            int cachehit = 0;
            int cachemiss = 0;
            for (int x = 0; x < width; x++)
                for (int y = 0; y < height; y++)
                {
                    Color c = bm.GetPixel(x, y);
                    Color c2 = colorCache.Get(c);
                    if (c2 == Color.Empty)
                    {
                        cachemiss++;
                        c2 = cube.FindMatch(c);
                        colorCache.Add(c, c2);
                    }
                    else cachehit++;
                
                    bm.SetPixel(x, y, c2);
                    if (dc.ContainsKey(c2)) dc[c2]++;
                    else dc.Add(c2, 1);
                }
            //            DateTime t1 = DateTime.Now;
            //            System.Windows.Forms.MessageBox.Show(
            //                String.Format("{0}", t1 - t0));
            return dc;
        }

        public void RemoveColor(Color c)
        {
            colors.RemoveAll(delegate(ColorItem ci){return ci.Color == c;});
            colorSearchList.Remove(c.ToArgb());
            OnColorsChanged();
        }
    }
}
